package com.swak.im;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;

import com.swak.Constants;
import com.swak.OS;
import com.swak.annotation.im.DistributedIm;
import com.swak.config.ChangeListener;
import com.swak.config.ConfigService;
import com.swak.im.enums.Role;
import com.swak.im.mapping.MappingService;
import com.swak.im.queue.QueueService;
import com.swak.im.storage.StorageService;
import com.swak.metrics.MetricsFactory;
import com.swak.registry.RegistryService;
import com.swak.registry.URL;
import com.swak.utils.JsonMapper;
import com.swak.utils.Maps;
import com.swak.utils.Sets;
import com.swak.utils.StringUtils;

import lombok.Getter;

/**
 * Im 服务
 * 
 * @author 618lf
 */
@Getter
public class ImService implements DisposableBean, ChangeListener {

	private AtomicBoolean started = new AtomicBoolean(false);
	private Set<ImWorker> workers = Sets.newHashSet();

	/**
	 * 默认走的配置是全局配置 和 本地配置
	 */
	private ImConfig config;

	@Autowired
	private StorageService storageService;
	@Autowired
	private RegistryService registryService;
	@Autowired
	private MappingService mappingService;
	@Autowired
	private QueueService queueService;
	@Autowired(required = false)
	private ConfigService configService;
	@Autowired(required = false)
	private MetricsFactory metricsFactory;

	private final URL moduleConfigURL;
	private final URL serverConfigURL;

	public ImService(ImConfig config) {
		this.config = config;
		this.moduleConfigURL = this.initModuleConfigURL();
		this.serverConfigURL = this.initServerConfigURL();
	}

	private URL initModuleConfigURL() {
		Map<String, String> parameters = Maps.newHashMap();
		parameters.put(Constants.Application_KEY, this.config.getApplicationName());
		parameters.put(Constants.Service_Key, this.config.getModule());
		parameters.put(Constants.Category_Key, Role.Config.name());
		return new URL(Constants.Registry_Im_Key, OS.ip(), 0, StringUtils.EMPTY, parameters);
	}

	private URL initServerConfigURL() {
		Map<String, String> parameters = Maps.newHashMap();
		parameters.put(Constants.Application_KEY, this.config.getApplicationName());
		parameters.put(Constants.Service_Key, this.config.getModule());
		parameters.put(Constants.Category_Key, Role.Config.name() + Constants.PATH_SEPARATE + OS.ip());
		return new URL(Constants.Registry_Im_Key, OS.ip(), 0, StringUtils.EMPTY, parameters);
	}

	public void start() {
		if (started.get()) {
			return;
		}
		try {
			this.parseConfig();
			this.doStart();
		} finally {
			started.compareAndSet(false, true);
		}
	}

	private void parseConfig() {
		try {
			if (this.configService != null) {
				String moduleConfig = this.configService.listen(this.moduleConfigURL, this::listenConfigChange);
				String serverConfig = this.configService.listen(this.serverConfigURL, this::listenConfigChange);
				this.configChange(moduleConfig);
				this.configChange(serverConfig);
			}
		} catch (Exception e) {
		}
	}

	private void listenConfigChange(String value) {
		if (started.get()) {
			this.configChange(value);
		}
	}

	@Override
	public synchronized void configChange(String config) {
		if (StringUtils.isBlank(config)) {
			return;
		}

		ImConfig imConfig = null;
		try {
			imConfig = JsonMapper.fromJson(config, ImConfig.class);
		} catch (Exception e) {
			return;
		}

		if (!(imConfig != null && imConfig.getConfigs() != null && !imConfig.getConfigs().isEmpty())) {
			return;
		}

		/*
		 * 合并配置
		 */
		this.config.merge(imConfig);

		/*
		 * 如果已经启动，则需要刷新Worker
		 */
		if (!started.get()) {
			return;
		}

		/*
		 * 刷新各个组件
		 */
		this.refreshWorkers();
	}

	private void doStart() {
		for (ImWorker worker : workers) {
			try {
				worker.start();
			} catch (Exception e) {
			}
		}
	}

	private void refreshWorkers() {
		for (ImWorker worker : workers) {
			worker.refresh(config);
		}
	}

	@Override
	public void destroy() throws Exception {
		for (ImWorker worker : workers) {
			try {
				worker.stop();
			} catch (Exception e) {
			}
		}
	}

	public ImWorker createWorker(DistributedIm anno, Object service) {
		Class<?> jobClass = service.getClass();
		Method executeMethod = null;
		Method[] methods = jobClass.getMethods();
		for (Method method : methods) {
			if (StringUtils.isNotBlank(anno.handleMethod()) && method.getName().equals(anno.handleMethod())) {
				executeMethod = method;
			}
		}
		Im im = Im.create(config, anno, service, executeMethod);
		ImWorker worker = ImWorker.create(im, this);
		workers.add(worker);
		return worker;
	}
}